﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.algorithms;
using snat.model;
using snat.util;
using System.IO;

namespace algorithms.algorithms.MessageRates {
    public class NodeBasedMessageRates
        : IAlgorithm, IChartRenderable, IReaderWriter {

        private TimeSpan intervalSpan;
        private DateTime currentInterval;
        private List<BaseNode> nodes;

        public Dictionary<BaseNode, Dictionary<DateTime, int>> Rate { get; private set; }
        public Dictionary<BaseNode, int> Count { get; private set; }

        public string Name {
            get { return "Node-Based Message Rates"; }
        }

        public NodeBasedMessageRates(TimeSpan _interval, List<BaseNode> _nodes = null) {
            this.intervalSpan = _interval;
            this.nodes = _nodes;
        }

        public void OnTapeLoaded() {
            if (nodes == null) {
                nodes = NetworkModel.Instance.Nodes.Values.ToList();
                foreach (BaseNode n in nodes) {
                    n.MessageSend += OnMessageSend;
                }
            }
            Rate = nodes.ToDictionary<BaseNode, BaseNode, Dictionary<DateTime, int>>(
                                        k => k, v => new Dictionary<DateTime, int>());
            Count = nodes.ToDictionary<BaseNode, BaseNode, int>(
                        k => k, v => 0);
            currentInterval = NetworkModel.Instance.StartTime;
            foreach (BaseNode node in nodes) {
                Rate[node].Add(currentInterval, 0);
            }
        }

        public void OnMessageSend(Message m, BaseNode n) {
            Count[n]++;
            while (m.Timestamp >= currentInterval + intervalSpan) {
                currentInterval += intervalSpan;
                foreach (BaseNode node in nodes) {
                    Rate[node].Add(currentInterval, 0);
                }
            }
            Rate[n][currentInterval] += m.Recipients.Count;
        }

        public void OnMessageProcessed(Message m) {
            
        }

        public void Output() {
            if (RenderChart != null) RenderChart(this);
        }

        public void Register() {
            NetworkModel.Instance.TapeLoaded += OnTapeLoaded;
            NetworkModel.Instance.MessageProcessed += OnMessageProcessed;
            foreach (BaseNode n in nodes) {
                n.MessageSend += OnMessageSend;
            }
            NetworkModel.Instance.TapePaused += Process;
            NetworkModel.Instance.TapeEnded += Process;
        }

        private void Process() {
            if (RenderChart != null) RenderChart(this);
        }

        public void DeRegister() {
            NetworkModel.Instance.TapeLoaded -= OnTapeLoaded;
            foreach (BaseNode n in nodes) {
                n.MessageSend -= OnMessageSend;
            }
            NetworkModel.Instance.MessageProcessed -= OnMessageProcessed;
            NetworkModel.Instance.TapePaused -= Process;
            NetworkModel.Instance.TapeEnded -= Process;
        }

        public event RenderChartDelegate RenderChart;

        public snat.util.DataChart GetRenderableChart() {
            int numOfWindows = 5;
            
            // For each of the top 5 nodes
            List<DataSeries> serieseses = new List<DataSeries>();
            foreach (KeyValuePair<BaseNode, int> n in Count.OrderByDescending(e => e.Value).Take(5)) {

                DataSeries temp = new DataSeries(n.Key.Name);
                List<DateTime> dates = Rate[n.Key].Keys.ToList();
                List<int> msgrates = Rate[n.Key].Values.ToList();
                int step = dates.Count / numOfWindows;
                if (step != 0) {
                    List<int> windowedAvg = new List<int>();
                    for (int i = 0; i < dates.Count; i++) {
                        windowedAvg.Add(msgrates[i]);
                        
                        // If we are at a step point, take the average rate of the window, add it to the series and reset the list
                        if (i % step == 0) {
                            temp.Series.Add(new DataPoint(dates[i].ToShortDateString(), windowedAvg.Average()));
                            windowedAvg.Clear();
                        } else {
                            // Else, if we are on the latest element, just take the running average
                            if (i == dates.Count - 1) {
                                temp.Series.Add(new DataPoint(dates[i].ToShortDateString(), windowedAvg.Average()));
                            }
                        }
                    }
                }
                serieseses.Add(temp);   
            }

            DataChartOptions chartOpts = new DataChartOptions("Top 5 Node Message Rates Chart");
            chartOpts.Description = "This chart gives the Message Rates for the top 5 nodes in the network, averaged over time-windows. Message Rate is a measure of the activity of a node over time. The dates on the x-axis signify the start of the respective time window.";
            return new DataChart(chartOpts, serieseses);

        }

        public void Write(string filePath) {
            using (StreamWriter output = new StreamWriter(filePath)) {              
                foreach (BaseNode n in Rate.Keys.ToList().OrderBy(x=>x.Name)) {
                    output.WriteLine("{0} :::", n.Name);
                    foreach (DateTime t in Rate[n].Keys) {
                        output.WriteLine("\t{0}, {1}", t.ToShortDateString(), Rate[n][t]);
                    }
                    output.WriteLine();
                }
            }
        }

        public void WriteToDir(string dirPath) {
            DirectoryInfo d = new DirectoryInfo(dirPath);
            if (!d.Exists) d.Create();
            foreach (BaseNode n in Rate.Keys) {
                try {
                    using (StreamWriter output = new StreamWriter(String.Format("{0}\\{1}.csv", dirPath, n.Name))) {
                        foreach (DateTime t in Rate[n].Keys) {
                            output.WriteLine("{0}, {1}", t.ToShortDateString(), Rate[n][t]);
                        }
                    }
                } catch (ArgumentException) {
                } catch (DirectoryNotFoundException) {
                } catch (NotSupportedException) {
                }
            }
        }

        public string Read(string filePath) {
            StringBuilder ret = new StringBuilder();
            using (StreamReader input = new StreamReader(filePath)) {
                while (!input.EndOfStream) {
                    ret.Append(input.ReadLine());
                }
            }
            return ret.ToString();
        }
    }
}

